#include "qq.h"
#include "crypt.h"
#include "misc.h"
#include "char_conv.h"
#include "infodlg.h"

const uint8 login_key[QQ_KEY_LENGTH] = {
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
};

// for QQ 2003, fixed value
const uint8 login_23_51[29] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x3A, 0x30, 0x9B, 0x69, 
  0x60, 0xA8, 0x32, 0x73, 0xB2, 0x22, 0x1F, 0xAA, 
  0x65, 0x6C, 0x09, 0xF8, 0x01
};

// machine id, should be different in different machine
const uint8 login_53_68[16] = {
  0x4d, 0xea, 0xb4, 0xa9, 0xe6, 0x85, 0xb0, 0x46,
  0xbb, 0x0b, 0x66, 0xa0, 0x1a, 0xf4, 0xb6, 0xb2
};

const uint8 send_im_tail[QQ_SEND_IM_AFTER_MSG_LEN] = {
  0x00, // end of msg                                 
  0x0a, // font attr, size=10, no bold, no italic, no underline 
  0x00, // font color red                             
  0x00, // font color green                           
  0xaa, // font color blue                            
  0x00, // unknown                                    
  0x86, // encoding, 0x86=GB, 0x00=EN                 
  0x22, // unknown, hardcode                          
  0xcb, 0xce, 0xcc, 0xe5, 0x0d // font name, related  
};

// some function needs to be defined in the earliest place
int qq_sendqueue_remove(struct qq_data *qd, int send_seq);

/*****************************************************************************/
// return a description string for command type
char *qq_get_type_str(int type) {
	switch (type) {
  case QQ_CMD_LOGOUT:
		return "QQ_CMD_LOGOUT";	
  case QQ_CMD_KEEP_ALIVE:
		return "QQ_CMD_KEEP_ALIVE";
  case QQ_CMD_UPDATE_INFO:
		return "QQ_CMD_UPDATE_INFO";
  case QQ_CMD_SEARCH_USER:
	  return "QQ_CMD_SEARCH_USER";
  case QQ_CMD_GET_USER_INFO:
    return "QQ_CMD_GET_USER_INFO";
	case QQ_CMD_ADD_FRIEND_WO_AUTH:
    return "QQ_CMD_ADD_FRIEND_WO_AUTH";
	case QQ_CMD_DEL_FRIEND:
    return "QQ_CMD_DEL_FRIEND";
	case QQ_CMD_ADD_FRIEND_W_AUTH:
    return "QQ_CMD_ADD_FRIEND_W_AUTH";
	case QQ_CMD_CHANGE_ONLINE_STATUS:
    return "QQ_CMD_CHANGE_ONLINE_STATUS";
	case QQ_CMD_ACK_SYS_MSG:
    return "QQ_CMD_ACK_SYS_MSG";
	case QQ_CMD_SEND_IM:
    return "QQ_CMD_SEND_IM";
	case QQ_CMD_RECV_IM:
    return "QQ_CMD_RECV_IM";
	case QQ_CMD_REMOVE_SELF:
    return "QQ_CMD_REMOVE_SELF";
	case QQ_CMD_LOGIN:
    return "QQ_CMD_LOGIN";
	case QQ_CMD_GET_FRIENDS_LIST:
    return "QQ_CMD_GET_FRIENDS_LIST";
	case QQ_CMD_GET_FRIENDS_ONLINE:
    return "QQ_CMD_GET_FRIENDS_ONLINE";
	case QQ_CMD_MACRO_CMD:
    return "QQ_CMD_MACRO_CMD";
	case QQ_CMD_RECV_MSG_SYS:
    return "QQ_CMD_RECV_MSG_SYS";
	case QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS:
		return "QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS";
	default:
		return "UNKNOWN_TYPE";
	}// switch (type)
}// qq_get_type_str

/*****************************************************************************/
// return the description string for client/server
char *qq_get_source_str(int source) {
	switch (source) {
	case QQ_CLIENT_062E:
		return "GB QQ2000c build 630";
	case QQ_CLIENT_072E:
		return "En QQ2000c build 305";
	case QQ_CLIENT_0801:
		return "En QQ2000c build 630";
	case QQ_CLIENT_0A1D:
		return "GB QQ2003c build 0808";
	case QQ_CLIENT_0B07:
		return "GB QQ2003c build 0925";
	case QQ_SERVER_0100:
		return "QQ Server 0100";
	default:
		return "QQ source unknown";
	}
}

/*****************************************************************************/
char *qq_get_recv_im_type_str(int type) {
	switch (type) {
	case QQ_RECV_IM_TO_BUDDY:
		return "QQ_RECV_IM_TO_BUDDY";
  case QQ_RECV_IM_TO_UNKNOWN:
    return "QQ_RECV_IM_TO_UNKNOWN";
	case QQ_RECV_IM_GROUP_INFO:
		return "QQ_RECV_IM_GROUP_INFO";
	case QQ_RECV_IM_ADD_TO_GROUP:
		return "QQ_RECV_IM_ADD_TO_GROUP";
	case QQ_RECV_IM_DEL_FROM_GROUP:
		return "QQ_RECV_IM_DEL_FROM_GROUP";
	case QQ_RECV_IM_APPLY_ADD_TO_GROUP:
		return "QQ_RECV_IM_APPLY_ADD_TO_GROUP";
	case QQ_RECV_IM_APPROVE_APPLY_ADD_TO_GROUP:
		return "QQ_RECV_IM_APPROVE_APPLY_ADD_TO_GROUP";
	case QQ_RECV_IM_REJCT_APPLY_ADD_TO_GROUP:
		return "QQ_RECV_IM_REJCT_APPLY_ADD_TO_GROUP";
	case QQ_RECV_IM_CREATE_GROUP:
		return "QQ_RECV_IM_CREATE_GROUP";
	case QQ_RECV_IM_SYS_NOTIFICATION:
		return "QQ_RECV_IM_SYS_NOTIFICATION";
	default:
		return "QQ_RECV_IM_UNKNOWN";
	}
}

/*****************************************************************************/
gboolean qq_check_packet_set_window
(uint16 seq, GaimConnection *gc) {
	struct qq_data *qd = gc->proto_data;
	uint8 *byte = &(qd->window[seq/8]);
	uint8 mask = (1 << (seq % 8));

	if ((*byte) & mask) return TRUE; // check mask
	(*byte) |= mask; return FALSE; // set mask
}

/*****************************************************************************/
// It used to be a random 16 bytes key
// now it has been fixed on 16 bytes 0x01 for QQ2003
void gen_login_key (uint8 *key) {
	memcpy(key, login_key, QQ_KEY_LENGTH);
}

/*****************************************************************************/
uint8 get_suffix_from_status(uint status) {
  return status == QQ_BUDDY_ONLINE_NORMAL ? QQ_ICON_SUFFIX_NORMAL :
				 status == QQ_BUDDY_ONLINE_AWAY ? QQ_ICON_SUFFIX_AWAY : 
				 QQ_ICON_SUFFIX_DEFAULT;
}

/*****************************************************************************/
struct qq_buddy *qq_locate_qq_buddy (GaimAccount *a, int uid) {
	char name[16];
	sprintf(name, "%d",uid);

	if (!a) return NULL;
	GaimBuddy *b = gaim_find_buddy(a, name);

	if (!b) return NULL;
	return (struct qq_buddy *)b->proto_data;
}

/*****************************************************************************/
void qq_update_buddy_contact
(GaimConnection *gc, struct qq_buddy *q_bud) {
	char uid[256];
	if (! q_bud) return;
	sprintf(uid, "%d", q_bud->uid);

	GaimBuddy *bud = gaim_find_buddy(gc->account, uid);
	if (! bud) return;	
	
	gaim_blist_alias_buddy(bud, q_bud->nickname);

	serv_got_update
	(gc, (char *)uid, q_bud->online, 0, q_bud->signon, q_bud->idle, bud->uc);
}

/*****************************************************************************/
void qq_create_packet_head_seq
(uint8 *buf, uint8 **cursor, uint16 cmd, uint16 seq) {
	*cursor = buf;
	qq_create_packet_b(buf, cursor, QQ_PACKET_TAG);
	qq_create_packet_w(buf, cursor, QQ_CLIENT);
	qq_create_packet_w(buf, cursor, cmd);
	qq_create_packet_w(buf, cursor, seq);
}

/*****************************************************************************/
void qq_create_packet_head
(uint8 *buf, uint8 **cursor, GaimConnection *gc, uint16 cmd) {
	struct qq_data *qd = gc->proto_data;
	if (cmd == QQ_CMD_LOGOUT) 
		qq_create_packet_head_seq(buf, cursor, cmd, 0xffff);
	else
		qq_create_packet_head_seq(buf, cursor, cmd, ++(qd->send_seq));
}

/*****************************************************************************/
// the lower level scket write, leave room for proxy support in the future
int qq_write
(int fd, uint8* data, int len) {
	return send(fd, data, len, 0);
}

/*****************************************************************************/
// for those need ack and resend if lost
void qq_send_packet
(GaimConnection *gc, uint8 *buf, int len, int type) {
	struct qq_data *qd = gc->proto_data;
	struct qq_sendpacket *p;
	
	gaim_debug(	GAIM_DEBUG_INFO, "QQ",
							"Send packet %s [%u] %04d bytes\n", 
							qq_get_type_str(type), qd->send_seq, len );
	qq_write(qd->fd, buf, len);
	
	p = g_new0(struct qq_sendpacket, 1);

	p->fd = qd->fd;
	p->type = type;
	p->send_seq = qd->send_seq;
	p->resend_times = 0;
	p->sendtime = time(NULL);
	p->buf = g_memdup(buf, len); // don't use g_strdup
	p->len = len;

	qd->sendqueue = g_list_append(qd->sendqueue, p);
}

/*****************************************************************************/
void qq_send_packet_change_status(GaimConnection *gc) {
  struct qq_data *qd = gc->proto_data;
  uint8 buf[MAX_PACKET_SIZE];
  uint8 *cursor;
  uint8 raw_data[4]; // only 4 bytes
  uint8 encrypted_data[80]; // it shourld be less than 32 bytes
  int encrypted_len;
	uint8 away_cmd;
  
	if (!qd->logged_in) return;                                 
	
	if (qd->status == QQ_SELF_STATUS_AVAILABLE)
		away_cmd = QQ_BUDDY_ONLINE_NORMAL;
	else if (qd->status == QQ_SELF_STATUS_INVISIBLE)
		away_cmd = QQ_BUDDY_ONLINE_INVISIBLE;
	else if 
		(	(qd->status == QQ_SELF_STATUS_AWAY) ||
		 	(qd->status == QQ_SELF_STATUS_IDLE) ||
			(qd->status == QQ_SELF_STATUS_CUSTOM)) 
		away_cmd = QQ_BUDDY_ONLINE_AWAY;
	else
		away_cmd = QQ_BUDDY_ONLINE_NORMAL;
	
  raw_data[0] = away_cmd;
	raw_data[1] = raw_data[2] = raw_data[3] = 0x00;

	qq_crypt(ENCRYPT, raw_data, 4,
           qd->session_key, encrypted_data, &encrypted_len);
  
	qq_create_packet_head(buf, &cursor, gc, QQ_CMD_CHANGE_ONLINE_STATUS);
  qq_create_packet_dw(buf, &cursor, (uint32)qd->uid);
  qq_create_packet_data(buf, &cursor, encrypted_data, encrypted_len);        
  qq_create_packet_b(buf, &cursor, QQ_PACKET_TAIL);           
                                                              
  qq_send_packet(gc, buf, cursor-buf, QQ_CMD_CHANGE_ONLINE_STATUS);
}

/*****************************************************************************/
void qq_send_packet_im
(GaimConnection *gc, int to_uid, const gchar *msg, int msg_len, int type) {
	struct qq_data *qd = gc->proto_data;
	uint8 buf[MAX_PACKET_SIZE];
	uint8 *cursor;
	uint16 client_tag = QQ_CLIENT;
	uint16 len_to_font_attr_tag = 0x000b;
	time_t now = time(NULL);
	int raw_len = QQ_SEND_IM_BEFORE_MSG_LEN + msg_len + QQ_SEND_IM_AFTER_MSG_LEN; 
	int encrypted_len = raw_len + 32;
	uint8 raw_data[raw_len];
	uint8 encrypted_data[encrypted_len];
	uint8 md5[16]; gen_session_md5(md5, qd->uid, qd->session_key);

	memcpy(raw_data, (uint32 *)&qd->uid, 4); //000-003, sender uid
	memcpy(raw_data+4, &to_uid, 4); //004-007, receiver uid
	memcpy(raw_data+8, &client_tag, 2); //008-009, sender QQ ver
	memcpy(raw_data+10, (uint32 *)&qd->uid, 4); //010-013, sender uid
	memcpy(raw_data+14, &to_uid, 4); // 014-017, receiver uid
	memcpy(raw_data+18, md5, 16); //018-033, md5(sender uid + session_key)
	memcpy(raw_data+34, &len_to_font_attr_tag, 2); //034-035
	memcpy(raw_data+36, &qd->send_seq, 2); // 036-037
	memcpy(raw_data+38, &now, 4); // 038-041, time
	raw_data[42] = 0x00;
	memcpy(raw_data+43, &qd->my_icon, 1); // 043
	raw_data[44] = raw_data[45] = raw_data[46] = 0x00;
	raw_data[47] = 0x01; // font attr tag, 01 means it has font attribute
	raw_data[48] = raw_data[49] = raw_data[50] = 0x00;
	raw_data[52] = type;
	memcpy(raw_data+53, msg, msg_len);

	// QQ_SEND_IM_BEFORE_MSG_LEN = 53 !
	memcpy(raw_data+QQ_SEND_IM_BEFORE_MSG_LEN+msg_len, 
					send_im_tail, QQ_SEND_IM_AFTER_MSG_LEN);

	qq_crypt(ENCRYPT, raw_data, raw_len, 
		qd->session_key, encrypted_data, &encrypted_len);
	
	qq_create_packet_head(buf, &cursor, gc, QQ_CMD_SEND_IM);
	qq_create_packet_dw(buf, &cursor, (uint32)qd->uid);
	qq_create_packet_data(buf, &cursor, encrypted_data, encrypted_len);
	qq_create_packet_b(buf, &cursor, QQ_PACKET_TAIL);

	qq_send_packet(gc, buf, cursor-buf, QQ_CMD_SEND_IM);
}

/*****************************************************************************/
void qq_send_packet_recv_im_ack
(GaimConnection *gc, uint16 seq, uint8 data[16]) {          
	struct qq_data *qd = gc->proto_data;
  uint8 buf[MAX_PACKET_SIZE];
  uint8 *cursor;
  uint8 encrypted_data[80]; // it shourld be less than 80 bytes
  int encrypted_len;
                                                              
  qq_crypt(ENCRYPT, data, 16,
           qd->session_key, encrypted_data, &encrypted_len);
 	
	// ack with its seq
  qq_create_packet_head_seq(buf, &cursor, QQ_CMD_RECV_IM, seq);
  qq_create_packet_dw(buf, &cursor, (uint32)qd->uid);
  qq_create_packet_data(buf, &cursor, encrypted_data, encrypted_len);
  qq_create_packet_b(buf, &cursor, QQ_PACKET_TAIL);
  
	qq_write(qd->fd, buf, cursor-buf); // no need ack again    
} 

/*****************************************************************************/
void qq_send_packet_ack_msg_sys
(GaimConnection *gc, uint8 *raw_data) {
	struct qq_data *qd = gc->proto_data;
  uint8 buf[MAX_PACKET_SIZE];
  uint8 *cursor;
  uint8 encrypted_data[80]; // it shourld be less than 80 bytes
  int encrypted_len;
                                                              
  qq_crypt(ENCRYPT, raw_data, strlen(raw_data),
           qd->session_key, encrypted_data, &encrypted_len);
 	
  qq_create_packet_head(buf, &cursor, gc, QQ_CMD_ACK_SYS_MSG);
  qq_create_packet_dw(buf, &cursor, (uint32)qd->uid);
  qq_create_packet_data(buf, &cursor, encrypted_data, encrypted_len);
  qq_create_packet_b(buf, &cursor, QQ_PACKET_TAIL);
  
	qq_write(qd->fd, buf, cursor-buf); // no need ack again    
}

/*****************************************************************************/
void qq_send_packet_add_buddy
(GaimConnection *gc, const char *uid) {
  struct qq_data *qd = gc->proto_data;
	uint8 buf[MAX_PACKET_SIZE];
  uint8 *cursor;
  uint8 raw_data[16];
  uint8 encrypted_data[80]; // it shourld be less than 32 bytes
  int encrypted_len;
	
	struct qq_add_buddy_request *req = 
		g_new0(struct qq_add_buddy_request, 1);
	
	sprintf(raw_data, "%s", uid);
	qq_crypt(ENCRYPT, raw_data, strlen(raw_data),
					 qd->session_key, encrypted_data, &encrypted_len);

  qq_create_packet_head(buf, &cursor, gc, QQ_CMD_ADD_FRIEND_WO_AUTH);       
  qq_create_packet_dw(buf, &cursor, (uint32)qd->uid);
  qq_create_packet_data(buf, &cursor, encrypted_data, encrypted_len);
  qq_create_packet_b(buf, &cursor, QQ_PACKET_TAIL);           
     
	req->seq = qd->send_seq; req->uid = atoi(uid);
	qd->add_buddy_request = g_list_append(qd->add_buddy_request, req);
  
	qq_send_packet(gc, buf, cursor-buf, QQ_CMD_ADD_FRIEND_WO_AUTH); 
}

/*****************************************************************************/
void qq_send_packet_get_info
(GaimConnection *gc, int uid, gboolean show_window) {
  struct qq_data *qd = gc->proto_data;
	uint8 buf[MAX_PACKET_SIZE];
  uint8 *cursor;
  uint8 raw_data[16];
  uint8 encrypted_data[80]; // it shourld be less than 32 bytes
  int encrypted_len;
	
	struct qq_info_query *query = g_new0(struct qq_info_query, 1);
	query->uid = uid; 
	query->show_window = show_window;
	qd->info_query = g_list_append(qd->info_query, query);
	
	sprintf(raw_data, "%d", uid);
	qq_crypt(ENCRYPT, raw_data, strlen(raw_data),
					 qd->session_key, encrypted_data, &encrypted_len);

  qq_create_packet_head(buf, &cursor, gc, QQ_CMD_GET_USER_INFO);       
  qq_create_packet_dw(buf, &cursor, (uint32)qd->uid);         
  qq_create_packet_data(buf, &cursor, encrypted_data, encrypted_len);       
  qq_create_packet_b(buf, &cursor, QQ_PACKET_TAIL);           
     
  qq_send_packet(gc, buf, cursor-buf, QQ_CMD_GET_USER_INFO);  	
}

/*****************************************************************************/
void qq_send_packet_keep_alive(GaimConnection *gc) {
  struct qq_data *qd = gc->proto_data;
  uint8 buf[MAX_PACKET_SIZE];
  uint8 *cursor;
  uint8 raw_data[4]; // only 4 bytes
  uint8 encrypted_data[80]; // it shourld be less than 32 bytes
  int encrypted_len;
	
	memcpy(raw_data, (uint32 *)&qd->uid, 4);
  qq_crypt(ENCRYPT, raw_data, 4,
           qd->session_key, encrypted_data, &encrypted_len);  
                                                              
  qq_create_packet_head(buf, &cursor, gc, QQ_CMD_KEEP_ALIVE);       
  qq_create_packet_dw(buf, &cursor, (uint32)qd->uid);         
  qq_create_packet_data(buf, &cursor, encrypted_data, encrypted_len);       
  qq_create_packet_b(buf, &cursor, QQ_PACKET_TAIL);      
     
	qq_send_packet(gc, buf, cursor-buf, QQ_CMD_KEEP_ALIVE);	
}

/*****************************************************************************/
void qq_send_packet_modify_info
(GaimConnection *gc, struct contact_info *info, gchar *new_passwd) {
	struct qq_data *qd = gc->proto_data;
	GaimAccount *a = gc->account;
	gchar *old_passwd = a->password;
	
	uint8 buf[MAX_PACKET_SIZE];
	uint8 raw_data[MAX_PACKET_SIZE-128]; // raw data is at most 128 bytes less
	uint8 encrypted_data[MAX_PACKET_SIZE-108]; //at most 16 bytes more after crypt
	uint8 *cursor = (uint8 *)raw_data;
	gint encrypted_len, i;
	gchar bar = 0x1f;
	gchar *info_field[QQ_CONTACT_FIELDS];
	memcpy(info_field, info, sizeof(gchar *) *QQ_CONTACT_FIELDS);
	
	if ((!new_passwd) || (strlen(new_passwd)==0)) {
		*cursor = bar; cursor ++;
	} else {// we gonna change
		qq_create_packet_data(raw_data, &cursor, old_passwd, strlen(old_passwd));
		*cursor = bar; cursor ++;
		qq_create_packet_data(raw_data, &cursor, new_passwd, strlen(new_passwd));
	}

	// important!, skip the first uid entry
	for(i=1; i<QQ_CONTACT_FIELDS; i++) {
		*cursor = bar; cursor ++;
		qq_create_packet_data
			(raw_data, &cursor, info_field[i], strlen(info_field[i]));
	}
	*cursor = bar; cursor ++;
	
	qq_crypt(ENCRYPT, raw_data, cursor-raw_data,
					 qd->session_key, encrypted_data, &encrypted_len);

  qq_create_packet_head(buf, &cursor, gc, QQ_CMD_UPDATE_INFO);        
  qq_create_packet_dw(buf, &cursor, (uint32)qd->uid);          
  qq_create_packet_data(buf, &cursor, encrypted_data, encrypted_len);        
  qq_create_packet_b(buf, &cursor, QQ_PACKET_TAIL);            
                                                               
  qq_send_packet(gc, buf, cursor-buf, QQ_CMD_UPDATE_INFO);
}

/*****************************************************************************/
void qq_send_packet_get_friends_online
(GaimConnection *gc, uint8 position) {
	struct qq_data *qd = gc->proto_data;
  uint8 buf[MAX_PACKET_SIZE];
  uint8 *cursor;
  uint8 raw_data[4]; // only 4 bytes
  uint8 encrypted_data[80]; // it shourld be less than 32 bytes
  int encrypted_len;
	
	raw_data[0] = 0x02; // get online friends, can be 0x03, unknown function
	raw_data[1] = position;
  raw_data[2] = 0x00; 
	raw_data[3] = 0x00;

  qq_crypt(ENCRYPT, raw_data, 4,                       
           qd->session_key, encrypted_data, &encrypted_len);
                                                       
  qq_create_packet_head(buf, &cursor, gc, QQ_CMD_GET_FRIENDS_ONLINE);
  qq_create_packet_dw(buf, &cursor, (uint32)qd->uid);  
  qq_create_packet_data(buf, &cursor, encrypted_data, encrypted_len);
  qq_create_packet_b(buf, &cursor, QQ_PACKET_TAIL);    
                                                       
  qq_send_packet(gc, buf, cursor-buf, QQ_CMD_GET_FRIENDS_ONLINE);
}

/*****************************************************************************/
// position starts with 0x0000, 
//server may return a position tag if list is too  long for one packet
void qq_send_packet_get_friends_list
(GaimConnection *gc, uint16 position) {
	struct qq_data *qd = gc->proto_data;
	uint8 buf[MAX_PACKET_SIZE];
	uint8 *cursor;
	uint8 raw_data[3]; // only 3 bytes
	uint8 encrypted_data[80]; // it shourld be less than 32 bytes
	int encrypted_len;
	
	memcpy(raw_data, &position, 2);
	raw_data[3] = 0x00; // end of it

	qq_crypt(ENCRYPT, raw_data, 3,
					 qd->session_key, encrypted_data, &encrypted_len);
	
	qq_create_packet_head(buf, &cursor, gc, QQ_CMD_GET_FRIENDS_LIST);
	qq_create_packet_dw(buf, &cursor, (uint32)qd->uid);
	qq_create_packet_data(buf, &cursor, encrypted_data, encrypted_len);
	qq_create_packet_b(buf, &cursor, QQ_PACKET_TAIL);

	qq_send_packet(gc, buf, cursor-buf, QQ_CMD_GET_FRIENDS_LIST);
}

/*****************************************************************************/
void qq_send_packet_logout(GaimConnection *gc) {
	struct qq_data *qd = gc->proto_data;
	uint8 buf[MAX_PACKET_SIZE];
	uint8 *cursor = buf;
	uint8 encrypted_data[80]; // it should be only 32 bytes
	int encrypted_len;
	
	// logout packet send my pwkey
	qq_crypt(ENCRYPT, qd->pwkey, QQ_KEY_LENGTH,
					 qd->session_key, encrypted_data, &encrypted_len);

	qq_create_packet_head(buf, &cursor, gc, QQ_CMD_LOGOUT);
	qq_create_packet_dw(buf, &cursor, (uint32)qd->uid);
	qq_create_packet_data(buf, &cursor, encrypted_data, encrypted_len);
	qq_create_packet_b(buf, &cursor, QQ_PACKET_TAIL);

	qq_write(qd->fd, buf, cursor-buf); // no ack, so write directly

	qd->logged_in = FALSE; // update login status
}

/*****************************************************************************/
void qq_send_packet_login(GaimConnection *gc) {
	struct qq_data *qd = gc->proto_data;
	uint8 buf[MAX_PACKET_SIZE];
	uint8 *cursor;
	gen_login_key(qd->inikey);
	uint8 raw_data[QQ_LOGIN_DATA_LENGTH];
	int encrypted_len;
	uint8 encrypted_data[QQ_LOGIN_DATA_LENGTH];
	
	// now generate the encrypted data
	// 000-015 use pwkey as key to encrypt empty string
	qq_crypt(ENCRYPT, "", 0, qd->pwkey, raw_data, &encrypted_len);
	// 016-016 
	raw_data[16] = 0x00;
	// 017-020, used to be IP, now zero
	*((uint32 *)(raw_data+17)) = 0x00000000;
	// 021-022, now zero
	*((uint16 *)(raw_data+21)) = 0x0000;
	// 023-051, fixed value, unknown
	memcpy(raw_data+23, login_23_51, 29);	
	// 052-052, login mode
	raw_data[52] = qd->login_mode;
	// 053-068, fixed value, maybe related to per machine
	memcpy(raw_data+53, login_53_68, 16);

	qq_crypt(ENCRYPT, raw_data, QQ_LOGIN_DATA_LENGTH, 
					qd->inikey, encrypted_data, &encrypted_len);
	qq_create_packet_head(buf, &cursor, gc, QQ_CMD_LOGIN);
	qq_create_packet_dw(buf, &cursor, (uint32) qd->uid);	
	qq_create_packet_data(buf, &cursor, qd->inikey, QQ_KEY_LENGTH);
	qq_create_packet_data(buf, &cursor, encrypted_data, encrypted_len); 	
	qq_create_packet_b(buf, &cursor, QQ_PACKET_TAIL);
	
	qq_send_packet(gc, buf, cursor-buf, QQ_CMD_LOGIN);
}

/*****************************************************************************/
void qq_process_recv_im_normal
(char *data, uint8 *cursor, int len, GaimConnection *gc) {
	
	if (cursor>=(uint8 *)(data+len-1)) {
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received normal IM empty\n");
		return;
	}
	uint32 sender_uid, receiver_uid;
	uint16 sender_ver, msg_seq, len_to_font_attr_tag, gaim_msg_type;
	uint8  session_md5[16], sender_icon, bar, msg_type, font_attr_tag;
	uint8  msg[QQ_MSG_IM_MAX], msg_len, temp[4];
	time_t send_time;

	qq_read_packet_w(data, &cursor, len, &sender_ver);
	qq_read_packet_dw(data, &cursor, len, &sender_uid);
	qq_read_packet_dw(data, &cursor, len, &receiver_uid);
	qq_read_packet_data(data, &cursor, len, session_md5, 16);
	qq_read_packet_w(data, &cursor, len, &len_to_font_attr_tag);

	if (len_to_font_attr_tag != 0x000b) {
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", 
			"Received normal IM, wrong len_to_font_attr_tag value [%d]",
			len_to_font_attr_tag);
		return;
	}

	qq_read_packet_w(data, &cursor, len, &msg_seq);
	qq_read_packet_dw(data, &cursor, len, &send_time);
	qq_read_packet_b(data, &cursor, len, &bar);
	qq_read_packet_b(data, &cursor, len, &sender_icon);
	qq_read_packet_data(data, &cursor, len, temp, 3);
	qq_read_packet_b(data, &cursor, len, &font_attr_tag);
	qq_read_packet_data(data, &cursor, len, temp, 4);
	qq_read_packet_b(data, &cursor, len, &msg_type);

	msg_len = strlen(cursor); // msg ends with 0x00

	qq_read_packet_data(data, &cursor, len, msg, msg_len);
	qq_read_packet_b(data, &cursor, len, &bar); // 0x00

	char buddy[16]; sprintf(buddy, "%d", (int)sender_uid);

	GaimBuddy *b = gaim_find_buddy(gc->account, buddy);
	if (!b) {
		GaimGroup * g;
		char group[256]; sprintf(group, _(QQ_GROUP_UNKNOWN));
		if (!(g = gaim_find_group(group))) {
			g = gaim_group_new(group);
			gaim_blist_add_group(g, NULL);
		}
		b = gaim_buddy_new(gc->account, (char *)buddy, NULL);
		gaim_blist_add_buddy(b, NULL, g, NULL);
	}
	
	if (msg_type == QQ_IM_AUTO_REPLY) 
		gaim_msg_type = GAIM_CONV_IM_AUTO_RESP;
	else 
		gaim_msg_type = 0;
	
	gchar *msg_locale = qq_to_locale(msg, msg_len, QQ_DEFAULT_LOCALE);
	gchar *msg_locale_encoded;	
	if (font_attr_tag) { // 0x00 means no attr
		msg_locale_encoded = qq_encode_to_html
			(cursor, (uint8 *)data+len-cursor, msg_locale, QQ_DEFAULT_LOCALE); 
		serv_got_im(gc, buddy, msg_locale_encoded, gaim_msg_type, send_time);
	} else
		serv_got_im(gc, buddy, msg_locale, gaim_msg_type, send_time);
	
	g_free(msg_locale);
}

/*****************************************************************************/
void qq_process_recv_im 
(uint8 *buf, int buf_len, uint16 seq, GaimConnection *gc) {
	struct qq_data *qd = gc->proto_data;
	int len = buf_len;
	char data[len];
	uint8 *cursor;
	uint8 ack[16];
	
	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
		cursor = data;
		memcpy(ack, data, 16); // ack is the first 16 bytes in msg
		qq_send_packet_recv_im_ack(gc, seq, data);
		
		if (len <= 17) 	gaim_debug
			(GAIM_DEBUG_WARNING, "QQ", "Received msg is too short\n");
		
		// now start process
		int sender_uid, receiver_uid;
		uint32 sender_ip, server_im_seq;
		uint16 sender_port, im_type;

		qq_read_packet_dw(data, &cursor, len, (uint32 *)&sender_uid);
		qq_read_packet_dw(data, &cursor, len, (uint32 *)&receiver_uid);
		qq_read_packet_dw(data, &cursor, len, &server_im_seq);
		qq_read_packet_dw(data, &cursor, len, &sender_ip);
		qq_read_packet_w(data, &cursor, len, &sender_port);
		qq_read_packet_w(data, &cursor, len, &im_type);

		if (receiver_uid != qd->uid) {// can not happen !!
			gaim_debug(GAIM_DEBUG_WARNING, "QQ",
				"Receive a msg, but it NOT to [%d], NOT me\n", receiver_uid);
			return;
		}
		
		switch (im_type) {
		case QQ_RECV_IM_TO_BUDDY:
		case QQ_RECV_IM_TO_UNKNOWN:
			qq_process_recv_im_normal((char *)data, cursor, len, gc);
		default:
			gaim_debug(GAIM_DEBUG_INFO, "QQ", 
				"Receive IM from [%d], type_id [%04x] %s\n", 
				sender_uid, im_type, qq_get_recv_im_type_str(im_type));
		}
	} else
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Error decrypt rev im\n");
}

/*****************************************************************************/
void qq_process_get_info_reply
(uint8 *buf, int buf_len, GaimConnection *gc) {
	struct qq_data *qd = gc->proto_data;
	struct contact_info *info = g_new0(struct contact_info, 1);
	struct _updating_contact_info *updateinfo;
	struct qq_info_query *query;
	struct qq_buddy *q_bud;
	GList *list=NULL, *query_list=NULL;
	gboolean show_window; // default
	
	int len = buf_len;
	char data[len];

	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
		gaim_debug(GAIM_DEBUG_INFO, "QQ", "Decrypt user info reply OK\n");
		char **ap, *val[QQ_CONTACT_FIELDS], *input=(char *)data;
		for (ap = val; (*ap = strsep(&input, "\x1e")) != NULL;)
			if (++ap >= &val[QQ_CONTACT_FIELDS]) break;

		memcpy(info, &val, sizeof(gchar*) * QQ_CONTACT_FIELDS);
		/*int i;
		for(i=0; i<QQ_CONTACT_FIELDS; i++) {
			printf("[%2d] ", i); hex_dump(val[i], strlen(val[i]));
		}*/
		if (qd->uid == atoi(info->uid)) {
			qd->my_icon = atoi(info->face);
			gchar *alias_locale = 
				qq_to_locale(info->nick, strlen(info->nick), QQ_DEFAULT_LOCALE);
			gaim_account_set_alias(gc->account, alias_locale); // my alias
			g_free(alias_locale); // needed for locale conversion
		} else { // it is not me, try update buddy list
			q_bud = qq_locate_qq_buddy
				((GaimAccount *)gc->account, atoi(info->uid));
			if (q_bud) {// I have this buddy
				q_bud->age = atoi(info->age);
				q_bud->gender = atoi(info->gender); 
				q_bud->icon = (uint8)(atoi(info->face)/3)+1;
				gchar *name_locale = 
					qq_to_locale(info->nick, -1, QQ_DEFAULT_LOCALE);
				strcpy(q_bud->nickname, name_locale); // now it is converted
				g_free(name_locale); // needed by g_convert
				q_bud->nickname_len = strlen(q_bud->nickname);
				qq_update_buddy_contact(gc, q_bud);
			}
		}
				
		query_list = qd->info_query; show_window = FALSE;
		while (query_list) {
			query = (struct qq_info_query *)query_list->data;
			if (query->uid == atoi(info->uid)) {
				show_window = query->show_window;
				g_free(query);
				qd->info_query = g_list_remove(qd->info_query, qd->info_query->data);
				break;
			}
			query_list = query_list->next;
		}

		if (! show_window) return;
		
		list = qd->updateinfo;
		while (list) {
			updateinfo = (struct _updating_contact_info *)(list->data);
			if (updateinfo->uid == atoi(info->uid)) {
				if (updateinfo->window)
					qq_refresh_contact_info_dialog(info, gc, updateinfo);
				else qq_show_contact_info_dialog(info, gc, updateinfo);
				break;
			} else list = list->next;
		}// while
	} else gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Error decrypt get info reply\n");
		
	g_free(info);
}

/*****************************************************************************/
void qq_process_send_im_reply
(uint8 *buf, int buf_len, uint16 seq, GaimConnection *gc) {
  struct qq_data *qd = gc->proto_data;
  int len = buf_len;
  char data[len];
  uint8 *cursor, reply;

	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {	
		cursor = data;
		qq_read_packet_b(data, &cursor, len, &reply);
		if (reply != QQ_SEND_IM_REPLY_OK) {
			gaim_debug(GAIM_DEBUG_WARNING, "QQ", 
				"Send IM [%d] fail, resend later\n", seq);
		} else {// remove from queue
			qq_sendqueue_remove(qd, seq);
			gaim_debug(GAIM_DEBUG_INFO, "QQ", 
				"IM [%d] ACK OK, remove from queue\n", seq);
		}
	} else
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Error decrypt send im reply\n");
}

/*****************************************************************************/
void qq_process_modify_info_reply
(uint8 *buf, int buf_len, uint16 seq, GaimConnection *gc) {
  struct qq_data *qd = gc->proto_data;
  int len = buf_len;
  char data[len];

	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {	
		if (qd->uid == atoi(data)) {// return should be my uid
			qq_sendqueue_remove(qd, seq);
			gaim_debug(GAIM_DEBUG_INFO, "QQ",
				"Update info [%d] ACK OK, remove from queue\n", seq);
		}
	} else
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Error decrypt modify info reply\n");

}

/*****************************************************************************/
void qq_process_change_status_reply 
(uint8 *buf, int buf_len, GaimConnection *gc) {
  struct qq_data *qd = gc->proto_data;
  int len = buf_len;
  char data[len];
	uint8 *cursor, reply;

	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
		cursor = data;
		qq_read_packet_b(data, &cursor, len, &reply);
		if (reply != QQ_CHANGE_ONLINE_STATUS_REPLY_OK) {
			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Change status fail\n");
			qq_send_packet_change_status(gc);
		} else
			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Change status OK\n");
	} else 
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Error decrypt chg status reply\n");
}

/*****************************************************************************/
void qq_process_keep_alive_reply 
(uint8 *buf, int buf_len, GaimConnection *gc) {
  struct qq_data *qd = gc->proto_data;
  int len = buf_len;
  char data[len];
	
	char **ap, *val[5], *input;
	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
		data[len]=0x00; //add stop
		input = (char *)data;
		for (ap = val; (*ap = strsep(&input, "\x1f")) != NULL;)
			if (++ap >= &val[5]) break;
		qd->all_online = (uint32)atoi(val[2]);
		strcpy(qd->my_ip, val[3]);
		qd->my_port = (uint16)atoi(val[4]);
	} else 
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Error decrypt keep alive reply\n");
}

/*****************************************************************************/
void qq_process_add_buddy_reply
(uint8 *buf, int buf_len, uint16 seq, GaimConnection *gc) {
  struct qq_data *qd = gc->proto_data;
  int len = buf_len;
  char data[len];
	int for_uid = 0;
	char msg[256];
	
	GList *list = qd->add_buddy_request;
	struct qq_add_buddy_request *req;
	while (list) {
		req = (struct qq_add_buddy_request *)list->data;
		if (req->seq == seq) {// reply to this
			for_uid = req->uid;
			g_free(req);
			qd->add_buddy_request = g_list_remove
				(qd->add_buddy_request, qd->add_buddy_request->data);
			break;
		}
		list = list->next;
	}

	if (! for_uid) {// we have no record for this
		gaim_debug(GAIM_DEBUG_WARNING, "QQ",
			"We have no record for add buddy reply [%d], discard\n", seq);
		return;
	} else 
		gaim_debug(GAIM_DEBUG_INFO, "QQ",
			"Add buddy reply [%d] is for id [%d]\n", seq, for_uid);

	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
	 	hex_dump(data, len);
	  if (! memchr(data, 0x1f, len)) {
			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Invalid add buddy reply\n");
	 		return;
		} else data[len] = 0x00; // add stop
    
		char **ap, *val[2], *input;
		input = data;
    for (ap = val; (*ap = strsep(&input, "\x1f")) != NULL;)
    	if (++ap >= &val[2]) break;
    char *uid=val[0], *reply=val[1];

		if (atoi(uid) != qd->uid) {
			gaim_debug(GAIM_DEBUG_WARNING, "QQ"
				"Add buddy reply is to [%s], not me!", uid);
			return;
		}

		if (atoi(reply) > 0) {// need auth
			gaim_debug(GAIM_DEBUG_WARNING, "QQ", 
				"Add buddy attempt fails, need authentication\n");
		} else { // add ok
			GaimAccount *a = gc->account; char buddy[16];
			struct qq_buddy *q_bud = g_new0(struct qq_buddy, 1);
			sprintf(buddy, "%d", for_uid);
			GaimBuddy *b = gaim_find_buddy(a, buddy);
			if (b) {
			  q_bud->uid = for_uid;
			  q_bud->status = QQ_BUDDY_ONLINE_OFFLINE;
			  q_bud->icon_suffix = get_suffix_from_status(q_bud->status);
        b->proto_data = q_bud;
      } else g_free(q_bud);
			qq_send_packet_get_info(gc, for_uid, FALSE);
			qq_send_packet_get_friends_online(gc, QQ_FRIENDS_ONLINE_POSITION_START);
			sprintf(msg, "用户%d已经成功加为好友", for_uid);
			gaim_notify_info(gc, utf8("添加用户"), utf8(msg), NULL);
		}
		
  } else 
    gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Error decrypt add buddy reply\n");
}

/*****************************************************************************/
void qq_process_msg_sys_being_added
(GaimConnection *gc, char *from, char *to, char *msg) {
	char message[256];
	sprintf(message, "[%s]加你[%s]为好友", from , to);
	gaim_notify_info(gc, NULL, utf8("系统消息"), utf8(message));
}

/*****************************************************************************/
void qq_process_msg_sys_add_contact_rejected
(GaimConnection *gc, char *from, char *to, char *msg) {
	char message[256];
	sprintf(message, 
		"[%s]拒绝加你[%s]。\n\n理由：%s", from, to,
		g_locale_to_utf8(msg, -1, NULL, NULL, NULL));
	gaim_notify_info(gc, NULL, utf8("系统消息"), utf8(message)); 
}

/*****************************************************************************/
void qq_process_msg_sys_add_contact_request
(GaimConnection *gc, char *from, char *to, char *msg) {
	char message[256];
	sprintf(message, "[%s]希望加你[%s]为好友。\n\n理由：%s", from , to,
    g_locale_to_utf8(msg, -1, NULL, NULL, NULL));
	gaim_notify_info(gc, NULL, utf8("系统信息"), utf8(message));

}

/*****************************************************************************/
void qq_proecess_msg_sys
(uint8 *buf, int buf_len, uint16 seq, GaimConnection *gc) {
  struct qq_data *qd = gc->proto_data;
  int len = buf_len;
  char data[len], bar=0x1e, *msg_locale;
	uint8 raw_data[80], *cursor;
	
  if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
		hex_dump(data, len);
		if (! memchr(data, '\x1f', len)) {
			gaim_debug(GAIM_DEBUG_WARNING, "QQ", 
				"Corrupted system message, no 0x1f in it\n");
			return;
		}
		char **ap, *val[4], *input;
		input = (char *) data; data[len]=0x00;// add stop for last string
		for (ap = val; (*ap = strsep(&input, "\x1f")) != NULL;)
			if (++ap >= &val[4]) break;
		char *code=val[0], *from=val[1], *to=val[2], *msg=val[3];
		
		cursor = raw_data;
		sprintf(cursor, "%s", code); cursor += strlen(code);
		*cursor = bar; cursor ++;
		sprintf(cursor, "%s", from); cursor += strlen(from);
		*cursor = bar; cursor ++;
		qq_create_packet_w(raw_data, &cursor, seq);
		*cursor = 0x00; // set stop

		qq_send_packet_ack_msg_sys(gc, raw_data);
		
		if (atoi(to) != qd->uid) {// not to me
			gaim_debug(GAIM_DEBUG_WARNING, "QQ", 
				"Recv sys msg to [%s], it is not to me!\n", to);
			return;
		}
    msg_locale = qq_to_locale(msg, -1, QQ_DEFAULT_LOCALE);
		switch (atoi(code)) {
		case QQ_MSG_SYS_BEING_ADDED:
			qq_process_msg_sys_being_added(gc, from, to, msg_locale);
			break;
		case QQ_MSG_SYS_ADD_CONTACT_REQUEST:
			qq_process_msg_sys_add_contact_request(gc, from, to, msg_locale);
			break;
		case QQ_MSG_SYS_ADD_CONTACT_APPROVED:
		break;
		case QQ_MSG_SYS_ADD_CONTACT_REJECTED:
			qq_process_msg_sys_add_contact_rejected(gc, from, to, msg_locale);
		break;
		default:
			gaim_debug(GAIM_DEBUG_WARNING, "QQ",
				"Recv unknown sys msg code: %s\n", code);
		}
    g_free(msg_locale);
	} else
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Error decrypt recv msg sys\n"); 	
}

/*****************************************************************************/
void qq_process_friend_change_status
(uint8 *buf, int buf_len, GaimConnection *gc) {
	struct qq_data *qd = gc->proto_data;
	int len = buf_len;
	uint8 data[len];
	uint8 *cursor;
	uint8 bar;
	uint32 ip;
	
	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
		gaim_debug(GAIM_DEBUG_INFO, "QQ", 
			"Decrypt buddy change status packet OK\n");
		struct qq_buddy_change_status *p = 
			g_new0(struct qq_buddy_change_status, 1);
		cursor = data;
		qq_read_packet_dw(data, &cursor, len, (uint32 *)&p->uid);
		qq_read_packet_b(data, &cursor, len, &bar);
		qq_read_packet_dw(data, &cursor, len, &ip);
		qq_read_packet_w(data, &cursor, len, &p->port);
		qq_read_packet_b(data, &cursor, len, &bar);
		qq_read_packet_b(data, &cursor, len, &p->status);
		qq_read_packet_w(data, &cursor, len, &p->unknown1);
		qq_read_packet_data(data, &cursor, len, p->unknown_key, QQ_KEY_LENGTH);
		qq_read_packet_dw(data, &cursor, len, &p->my_uid);

		// verify reading is complete
		if ((!p->uid)||(!p->my_uid)) {g_free(p); return;}

		struct qq_buddy *q_bud = qq_locate_qq_buddy(gc->account, p->uid);
		if (q_bud) {
			gen_ip_str(q_bud->ip, ip);
			q_bud->port = p->port;
			q_bud->status = p->status;

			if (q_bud->status == QQ_BUDDY_ONLINE_NORMAL) q_bud->online = TRUE;
			else if (q_bud->status == QQ_BUDDY_ONLINE_AWAY) q_bud->online = TRUE;
			else if (q_bud->status == QQ_BUDDY_ONLINE_OFFLINE) q_bud->online = FALSE;
			else gaim_debug(GAIM_DEBUG_WARNING, "QQ", 
					"Unknown status code [%d] from [%d]\n", q_bud->status, q_bud->uid);
      
			q_bud->icon_suffix = get_suffix_from_status(q_bud->status);
			qq_update_buddy_contact(gc, q_bud);
		}
		g_free(p);
	}	else gaim_debug(GAIM_DEBUG_WARNING, "QQ", 
					"Error decrypt buddy status change packet\n");
}

/*****************************************************************************/
void qq_process_get_friends_online_reply
(uint8 *buf, int buf_len, GaimConnection *gc) {
  struct qq_data *qd = gc->proto_data;
  int len = buf_len;
  uint8 position;
  uint8 data[len]; // at most this size
  uint8 *cursor;
	uint8 bar;
	uint32 ip;
	
	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
		cursor = data;
		qq_read_packet_b(data, &cursor, len, &position);
		
		struct qq_friends_online_entry *fe;
		fe = g_new0(struct qq_friends_online_entry, 1);
	
		while (cursor<(data+len-1)) {
			qq_read_packet_dw(data, &cursor, len, (uint32 *)&fe->uid);
			qq_read_packet_b(data, &cursor, len, &bar);
			qq_read_packet_dw(data, &cursor, len, &ip);
			qq_read_packet_w(data, &cursor, len, &fe->port);
			qq_read_packet_b(data, &cursor, len, &bar);
			qq_read_packet_b(data, &cursor, len, &fe->status);
			qq_read_packet_w(data, &cursor, len, &fe->unknown1);
			qq_read_packet_data
				(data, &cursor, len, (uint8 *)fe->unknown_key, QQ_KEY_LENGTH);
			qq_read_packet_b(data, &cursor, len, &bar);
			qq_read_packet_b(data, &cursor, len, &fe->flag);
	  
			if (! fe->uid) continue;
			// update buddy information
			struct qq_buddy *q_bud = qq_locate_qq_buddy(gc->account, fe->uid);
			if (q_bud) {
				gen_ip_str(fe->ip, ip);
				strcpy(q_bud->ip, fe->ip);
				q_bud->port = fe->port;
				q_bud->online = TRUE;
				q_bud->status = fe->status;
				q_bud->icon_suffix = get_suffix_from_status(q_bud->status);
				q_bud->flag = fe->flag;
				qq_update_buddy_contact(gc, q_bud);
			} // if
		}// while cursor
		
		g_free(fe);
		
		if (position == QQ_FRIENDS_ONLINE_POSITION_END) {
			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Get friends online done\n");
		} else qq_send_packet_get_friends_online(gc, position);
		
	} else gaim_connection_error(gc, _("Error decrypt buddies online"));
}

/*****************************************************************************/
// only at login phase
void qq_process_get_friends_list_reply
(uint8 *buf, int buf_len, GaimConnection *gc) {
	struct qq_data *qd = gc->proto_data;	
	int len = buf_len;
	uint16 position;
	uint8 data[len]; // at most this size
	uint8 *cursor;
	uint8 bar; // bar is 0x00

	char *group_name[QQ_NAME_LENGTH_MAX];
	char *buddy_name[QQ_NAME_LENGTH_MAX];
	char *group = (char *)group_name; // to save some trouble of type conversion
	char *buddy = (char *)buddy_name;

	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
		cursor = data;
		qq_read_packet_w(data, &cursor, len, &position);
		
		// add a default group for this QQid
		sprintf(group, _(QQ_GROUP_NAME));
		GaimGroup *g;
		if (!(g = gaim_find_group(group))) {
			g = gaim_group_new(group);
			gaim_blist_add_group(g, NULL);
			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add new group: %s\n", group);
		}

		// the following data is freinds list in this packet
		while (cursor<(data+len-1)) {
			struct qq_buddy *fe;  // friend entry
			fe = g_new0(struct qq_buddy, 1);
			qq_read_packet_dw(data, &cursor, len, (uint32 *)&fe->uid);
			qq_read_packet_b(data, &cursor, len, &bar);// 0x00 for uid
			qq_read_packet_b(data, &cursor, len, &fe->icon);
			qq_read_packet_b(data, &cursor, len, &fe->age);
			qq_read_packet_b(data, &cursor, len, &fe->gender);
			qq_read_packet_b(data, &cursor, len, &fe->nickname_len);
			qq_read_packet_data
				(data, &cursor, len, (uint8 *)&fe->nickname, fe->nickname_len);
			qq_read_packet_b(data, &cursor, len, &fe->flag);
			qq_read_packet_b(data, &cursor, len, &bar);// 0x00 for this entry
			fe->nickname[fe->nickname_len]=0x00; // stop sign for string
		
			if (! fe->uid) {g_free(fe); continue;}
			fe->icon = (uint8)(fe->icon / 3) + 1;
			fe->icon_suffix = QQ_ICON_SUFFIX_OFFLINE;
			fe->online = FALSE;			
			fe->ip[0] = 0x00;
			fe->port = 0x0000;
			fe->status = 0x00;
			fe->signon = 0;
			fe->idle = 0;

			gchar *name_locale = 
					qq_to_locale(fe->nickname, fe->nickname_len, QQ_DEFAULT_LOCALE);
			strcpy(fe->nickname, name_locale); // now it is converted
			fe->nickname_len = strlen(fe->nickname);
			g_free(name_locale); // needed by g_convert
			
			sprintf(buddy, "%d", fe->uid);
			GaimBuddy *b = gaim_find_buddy(gc->account, buddy);
			
			if (!b) {
				b = gaim_buddy_new(gc->account, buddy, name_locale);
				gaim_blist_add_buddy(b, NULL, g, NULL);
				gaim_debug(GAIM_DEBUG_WARNING, "QQ", 
					"Add new buddy: [%s] %s\n", buddy, name_locale);
			} 
			b->proto_data = fe;
			qq_update_buddy_contact(gc, fe);
		}// while
		
		if (position == QQ_FRIENDS_LIST_POSITION_END) {
			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Get friends list done\n");
			qq_send_packet_get_friends_online(gc, QQ_FRIENDS_ONLINE_POSITION_START);
		}
		else
			qq_send_packet_get_friends_list(gc, position);
	
	} else
		gaim_connection_error(gc, _("Error decrypt buddies list"));;
}

/*****************************************************************************/
void qq_common_clean(GaimConnection *gc);
void qq_got_login(gpointer data, gint source, GaimInputCondition cond);
// start a new connection 
int qq_new_connection
(GaimConnection *gc, char *server, uint16 port) {
	int fd = -1;
	struct sockaddr_in sin;

	qq_common_clean(gc);

	if (!inet_aton(server, &sin.sin_addr)) {
    struct hostent *hp;
    if(!(hp = gethostbyname(server))) {
      gaim_debug(GAIM_DEBUG_ERROR, "QQ Redirect",
             "gethostbyname(\"%s\", %d) failed: %s\n",
             server, port, hstrerror(h_errno));
      return 1;
    }
    memset(&sin, 0, sizeof(struct sockaddr_in));
    memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
    sin.sin_family = hp->h_addrtype;
  } else
    sin.sin_family = AF_INET;
  sin.sin_port = htons(port);
		
	gaim_debug(GAIM_DEBUG_INFO, "QQ Redirect", "Using UDP without proxy\n");
	fd = socket(AF_INET, SOCK_DGRAM, 0);
	
	if (fd < 0) {
	  gaim_debug(GAIM_DEBUG_ERROR, "QQ Redirect",
	    "Unable to create socket: %s\n", strerror(errno));
		return 1;
	}

	fcntl(fd, F_SETFL, O_NONBLOCK);

	if (connect(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr)) < 0) {
    if ((errno == EINPROGRESS) || (errno == EINTR)) 
      gaim_debug(GAIM_DEBUG_WARNING, "QQ Redirect",
             "Connect would have blocked.\n");
    else {
      gaim_debug(GAIM_DEBUG_ERROR, "QQ Redirect",            
             "Connect failed (errno %d)\n", errno);    
      close(fd);
      return 1;
    }
  }
  else {                                               
    unsigned int len;                                  
    int error = ETIMEDOUT;                             
    gaim_debug(GAIM_DEBUG_MISC, "QQ Redirect", "Connect didn't block.\n");
    len = sizeof(error);
    if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
      gaim_debug(GAIM_DEBUG_ERROR, "QQ Redirect", "getsockopt failed.\n");
      close(fd);
      return 1;
    }
    fcntl(fd, F_SETFL, 0);                             
	}

	qq_got_login((gpointer)gc, (gint)fd, NULL);
	return 0;
}

/*****************************************************************************/
int qq_process_login_reply
(char *buf, int buf_len, GaimConnection *gc) {
	struct qq_data *qd = gc->proto_data;
	struct qq_login_reply_ok_packet lrop;
	struct qq_login_reply_redirect_packet lrrp;

	int len = buf_len;
	uint8 data[len]; // decrypted, at most buf_len long
	uint8 *cursor, *new_server;
	char *new_server_str;
	
	if (qq_crypt(DECRYPT, buf, buf_len, qd->pwkey, data, &len)) {
		// should be able to decrypt with pwkey
		gaim_debug(GAIM_DEBUG_INFO, "QQ", 
			"Succeed decrypting login reply packet with pwkey, %d bytes\n", len);
		cursor = data;
		qq_read_packet_b(data, &cursor, len, &lrop.result);
		qq_read_packet_data(data, &cursor, len, lrop.session_key, QQ_KEY_LENGTH);
		if (lrop.result == QQ_LOGIN_REPLY_OK) {
			memcpy(qd->session_key, lrop.session_key, QQ_KEY_LENGTH);
			gaim_debug(GAIM_DEBUG_INFO, "QQ", 
				"Get session_key: %s\n", qd->session_key);
			gaim_connection_set_state(gc, GAIM_CONNECTED);
		 	serv_finish_login(gc);
		 	qd->logged_in = TRUE;
			qd->status = QQ_SELF_STATUS_AVAILABLE;
			qq_send_packet_get_info(gc, qd->uid, FALSE);
			// now start getting friends list, position starts from 0
			qq_send_packet_get_friends_list(gc, QQ_FRIENDS_LIST_POSITION_START); 
		} else {
			// should not come here
			gaim_debug(GAIM_DEBUG_ERROR, "QQ",
				"Unknown login reply code : %d\n", lrop.result);
			gaim_connection_error(gc, _("Unknown login reply code"));
			return 1;
		}
	} else { // decrypt error
		len = buf_len; // reset len, for next decrypt
		// decrypt will fail if len is too short
		if (qq_crypt(DECRYPT, buf, buf_len, qd->inikey, data, &len)) { 	
			// decrypt ok with inipwd, it is password error
			gaim_debug(GAIM_DEBUG_INFO, "QQ", 
				"Succeed decrypting login reply packet with inikey, %d bytes\n", len); 
			cursor = data; 
			switch (data[0]) {
				case QQ_LOGIN_REPLY_REDIRECT:
					qq_read_packet_b(data, &cursor, len, &lrrp.result);
					qq_read_packet_dw(data, &cursor, len, &lrrp.uid);
					qq_read_packet_data(data, &cursor, len, lrrp.new_server_ip, 4);
					qq_read_packet_w(data, &cursor, len, &lrrp.new_server_port);
					new_server = lrrp.new_server_ip;
					// new connection
				 	asprintf(&new_server_str, "%d.%d.%d.%d",
					 	new_server[0], new_server[1],new_server[2], new_server[3]);
					gaim_debug(GAIM_DEBUG_WARNING, "QQ",
						"Redirected to new server: %s:%d\n",
						new_server_str, lrrp.new_server_port);
					gaim_connection_update_progress
						(gc, _("Redirect"), 2, QQ_CONNECT_STEPS);
					qq_new_connection(gc, new_server_str, lrrp.new_server_port);	
					g_free(new_server_str); // required by asprintf
					return 0;
				case QQ_LOGIN_REPLY_PWD_ERROR:
					gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Wrong password\n");
					gaim_connection_error(gc, _("Wrong password"));
					return 1;
				default:
					gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
						"Unknown reply code: %d\n", data[0]);
					gaim_connection_error (gc, _("Unknown reply code"));
					return 1;
			}
		} else {
			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Failed decrypting with inikey\n");
		  gaim_connection_error(gc, _("Failed decryption"));
			return 1;
		}
	}
	return 0;
}

/*****************************************************************************/
void qq_packet_process
(uint8 *buf, int buf_len, GaimConnection *gc) {
	struct qq_data *qd = gc->proto_data;
	struct qq_rev_msg_header header;
	int len;
	uint8 *cursor;

	if (buf_len < sizeof(struct qq_rev_msg_header)) {
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", 
			"Received packet is too short, drop it!");
		return;
	}

	cursor = buf;
	qq_read_packet_b(buf, &cursor, buf_len, &header.header_tag);
	qq_read_packet_w(buf, &cursor, buf_len, &header.source_tag);
	qq_read_packet_w(buf, &cursor, buf_len, &header.cmd);
	qq_read_packet_w(buf, &cursor, buf_len, &header.seq);

	if ((buf[buf_len-1] != QQ_PACKET_TAIL) || 
			(header.header_tag != QQ_PACKET_TAG)) {
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Bad packet head/tail tag, drop it");
		return;
	}
	
	gaim_debug(GAIM_DEBUG_INFO, "QQ",
		"Receive %s [%d] %d bytes, from (%s)\n", 
		qq_get_type_str(header.cmd), header.seq, 
		buf_len, qq_get_source_str(header.source_tag));
	
	if (qq_check_packet_set_window(header.seq, gc)) {
		gaim_debug(GAIM_DEBUG_WARNING, "QQ",
			"Packet %s [%d] is duplicated, discard...\n", 
			qq_get_type_str(header.cmd), header.seq);
		return;
	}
		
	//can not use sizeof(struct server_header), not accurate	
	len = buf_len - (cursor-buf) - 1;// also remove tail tag, 
	
	// if it is a ack packet
	switch (header.cmd) {
	case QQ_CMD_RECV_IM:
	case QQ_CMD_RECV_MSG_SYS:
	case QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS:
		// server intiated packet, we need to send ack
		break;
	case QQ_CMD_UPDATE_INFO:
	case QQ_CMD_SEND_IM:
		// we need to change reply status, remove only if OK
		gaim_debug(GAIM_DEBUG_INFO, "QQ", 
			"Msg %s [%d] got ACK, check reply status\n", 
			qq_get_type_str(header.cmd), header.seq);
		break;
	default: // ack packet, we need to update sendqueue
		qq_sendqueue_remove(qd, header.seq);
		gaim_debug(GAIM_DEBUG_INFO, "QQ",
			"Msg %s [%d] got ACK, remove from queue\n",
			qq_get_type_str(header.cmd), header.seq);
		break;
	}
	
	// now process the packet
	switch (header.cmd) {
	case QQ_CMD_KEEP_ALIVE:
		qq_process_keep_alive_reply(cursor, len, gc);
		break;
	case QQ_CMD_UPDATE_INFO:
		qq_process_modify_info_reply(cursor, len, header.seq, gc);
		break;
  case QQ_CMD_ADD_FRIEND_WO_AUTH:
    qq_process_add_buddy_reply(cursor, len, header.seq, gc);
    break;
	case QQ_CMD_GET_USER_INFO:
		qq_process_get_info_reply(cursor, len, gc);
		break;
	case QQ_CMD_CHANGE_ONLINE_STATUS:
		qq_process_change_status_reply(cursor, len, gc);
		break;
	case QQ_CMD_SEND_IM:
		qq_process_send_im_reply(cursor, len, header.seq, gc);
		break;
	case QQ_CMD_RECV_IM:
		qq_process_recv_im(cursor, len, header.seq, gc);
		break;
	case QQ_CMD_LOGIN:
		qq_process_login_reply(cursor, len, gc);
		break;
	case QQ_CMD_GET_FRIENDS_LIST:
		qq_process_get_friends_list_reply(cursor, len, gc);
		break;
	case QQ_CMD_GET_FRIENDS_ONLINE:
		qq_process_get_friends_online_reply(cursor, len, gc);
		break;
	case QQ_CMD_RECV_MSG_SYS:
		qq_proecess_msg_sys(cursor, len, header.seq, gc);
		break;
	case QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS:
		qq_process_friend_change_status(cursor, len, gc);
		break;
	default:
		gaim_debug(GAIM_DEBUG_WARNING, "QQ",
			"Msg %s [%d] -> default\n",
			qq_get_type_str(header.cmd), header.seq);
		hex_dump(buf, buf_len);
		break;
	}// switch

}

/*****************************************************************************/
void qq_updating_contact_info_free(struct qq_data *qd) {
	struct _updating_contact_info *info;
	int i = 0; if (! qd) return;
	
	while (qd->updateinfo) {
		i++;
		info = (struct _updating_contact_info *) qd->updateinfo->data;
		if (info->window) gtk_widget_destroy(info->window);
		else {
			g_free(qd->updateinfo->data);
			qd->updateinfo = g_list_remove(qd->updateinfo, qd->updateinfo->data);
		}
	}
	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d update_info are freed\n", i);
}

/*****************************************************************************/
void qq_pending
(gpointer data, gint source, GaimInputCondition cond) {
	GaimConnection *gc = data;
	struct qq_data *qd = gc->proto_data;
	uint8 buf[MAX_PACKET_SIZE];
	uint8 *cursor = buf;
	int len;

	len = read(qd->fd, buf, sizeof(buf));

	if (len <= 0) {
		gaim_connection_error(gc, _("Unable to read"));
		return;
	}

	qq_packet_process(cursor, len, gc);
}

/*****************************************************************************/
int qq_sendqueue_remove
(struct qq_data *qd, int send_seq) {
	GList *list = qd->sendqueue;
	struct qq_sendpacket *p;
	while (list) {
		p = (struct qq_sendpacket *)(list->data);
		if  (p->send_seq == send_seq) {
			g_free(p->buf);
			g_free(p);
			qd->sendqueue = g_list_remove(qd->sendqueue, list->data);
			return 1;
		}
		list = list->next;
	}// whiile
	return 0;
}

/*****************************************************************************/
void qq_add_buddy_request_free(struct qq_data *qd) {
	struct qq_add_buddy_request *p;
	int i = 0;
	while (qd->add_buddy_request) {
		i ++;
		p = (struct qq_add_buddy_request *)(qd->add_buddy_request->data);
		g_free(p);
		qd->add_buddy_request = g_list_remove
			(qd->add_buddy_request, qd->add_buddy_request->data);
	}
	gaim_debug(GAIM_DEBUG_INFO, "QQ",
		"%d add buddy requests are freed!\n", i);
}
/*****************************************************************************/
void qq_info_query_free(struct qq_data *qd) {
	struct qq_info_query *p;
	int i = 0;
	while (qd->info_query) {
		i ++;
		p = (struct qq_info_query *)(qd->sendqueue->data);
		g_free(p);
		qd->info_query = g_list_remove(qd->info_query, qd->info_query->data);
	}
	gaim_debug(GAIM_DEBUG_INFO, "QQ",
		"%d info queries are freed!\n", i);
}

/*****************************************************************************/
void qq_sendqueue_free(struct qq_data *qd) {
	struct qq_sendpacket *p;
	int i = 0;
	while (qd->sendqueue) {
		i ++;
		p = (struct qq_sendpacket *)(qd->sendqueue->data);
		g_free(p->buf);
		g_free(p);
		qd->sendqueue = g_list_remove(qd->sendqueue, qd->sendqueue->data);
	}
	gaim_debug(GAIM_DEBUG_INFO, "QQ",
		"%d packets in sendqueue are freed!\n", i);
}

/*****************************************************************************/
void qq_send_again(struct gc_and_packet *gp) {
	struct qq_data *qd = gp->gc->proto_data;
	GList *list;

	list = g_list_find(qd->sendqueue, gp->packet);
	if (list) {
		qq_write(gp->packet->fd, gp->packet->buf, gp->packet->len);
		gp->packet->resend_times = 0;
		gp->packet->sendtime = time(NULL);
	}
	g_free(gp);
}

/*****************************************************************************/
void qq_send_cancel(struct gc_and_packet *gp) {
	struct qq_data *qd = gp->gc->proto_data;
  GList *list;

  list = g_list_find(qd->sendqueue,gp->packet);
  if (list) {
    qq_sendqueue_remove(qd, gp->packet->send_seq);
  }
  g_free(gp);
}

/*****************************************************************************/
gboolean qq_sendqueue_timeout_callback(gpointer data) {
	GaimConnection *gc = (GaimConnection *)data;
	struct qq_data *qd = gc->proto_data;

	GList *list;
	struct qq_sendpacket *p;
	time_t nowtime;
	
	if (! qd->sendqueue) return TRUE; // empty queue

	nowtime = time(NULL);
	list = qd->sendqueue;

	while(list) { // remove all packet whose resend_times == -1
		p = (struct qq_sendpacket *) (list->data);
		if (p->resend_times == -1) { // to remove
			g_free(p->buf);
			g_free(list->data);
			qd->sendqueue = g_list_remove(qd->sendqueue, list->data);
			list = qd->sendqueue;
		} else {
			list = list->next;
		}
	} // while
	
	list = qd->sendqueue;
	while (list) {
		p = (struct qq_sendpacket *)(list->data);
		if (p->resend_times >= QQ_MAX_RESEND) {
			if (p->resend_times == QQ_MAX_RESEND) {
				switch (p->type) {
				case QQ_CMD_KEEP_ALIVE:
					if (qd->logged_in) {
						gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Connection lost!\n");
						gaim_connection_error(gc, _("Connection lost!\n"));
						qd->logged_in = FALSE;
					}
					p->resend_times = -1; // ready to remove
					break;
				case QQ_CMD_LOGIN:
					if (! qd->logged_in) // cancel logging progress
						gaim_connection_error (gc, _("Login failed,  no reply!"));
					break;
				case QQ_CMD_UPDATE_INFO:
					gaim_notify_warning(gc, NULL, 
						_("Connection timeout!"),
						_("User info is not updated"));
					p->resend_times = -1;	
					break;
				default: {
					struct gc_and_packet *gp;
					gp = g_new0(struct gc_and_packet, 1);
					gp->gc = gc;
					gp->packet = p;
					gaim_request_action(gc, NULL, _("Send packet"), 
						_("Some packets are lost,\nwill you send again?"), 
						0, gp, 2,
						_("Send"), G_CALLBACK(qq_send_again),
						_("Cancel"), G_CALLBACK(qq_send_cancel));
					p->resend_times ++; // will send once more, but only once
				}} // switch
			} // resend_times == QQ_MAX_RESEND
		} else { // resend_times < 3, so sent it again
			int wait_time = (int)(QQ_SENDQUEUE_TIMEOUT/1000);
			if (difftime(nowtime, p->sendtime) >= (wait_time*(p->resend_times+1))) {
				qq_write(p->fd, p->buf, p->len);
				p->resend_times ++;
				gaim_debug(GAIM_DEBUG_INFO, "QQ",
					"packet [%d] send again for %d times!\n", 
					p->send_seq, p->resend_times);
			}
		} // if resend_times >= 3
		list = list->next;
	}
	return TRUE;
}

/*****************************************************************************/
// process login process
void qq_got_login
(gpointer data, gint source, GaimInputCondition cond) {
	GaimConnection *gc = data;
  struct qq_data *qd = (struct qq_data *)gc->proto_data;
  gchar *buf;
	uint8 *passwd;
  uint8 pwkey[16]; // md5 processed passwd
  
  if (!g_list_find(gaim_connections_get_all(), gc)) {
    close(source);
    return;
  }
  
	if (source < 0) {
    gaim_connection_error(gc, _("Unable to connect."));
    return;
  }
 
 	// we use random seq, to minimize duplicated packets
	qd->send_seq = random() & 0x0000ffff;
  
	// set md data, 
	qd->fd = source;
	qd->logged_in = FALSE;
	qd->uid = atoi(gaim_account_get_username(gc->account));
	qd->login_mode = QQ_LOGIN_MODE_NORMAL; //default, change later	
	// now generate md5 processed passwd
	passwd = (uint8 *)gaim_account_get_password(gc->account);
	gen_pwkey(passwd, pwkey);
	memcpy(qd->pwkey, pwkey, 16);
	
  /* Update the login progress status display */
  buf = g_strdup_printf("Logging in %d", qd->uid);
	qq_send_packet_login(gc);
  gaim_connection_update_progress(gc, buf, 1, QQ_CONNECT_STEPS);
  g_free(buf);

	qd->sendqueue_timeout = g_timeout_add
		(QQ_SENDQUEUE_TIMEOUT, qq_sendqueue_timeout_callback, gc);
	gc->inpa = gaim_input_add(qd->fd, GAIM_INPUT_READ, qq_pending, gc);
}

/*****************************************************************************/
void qq_common_clean(GaimConnection *gc) {
  struct qq_data *qd = gc->proto_data;
  int i;

  if ((qd->fd >= 0) && (qd->logged_in)) {
    for(i=0; i<QQ_MAX_RESEND; i++) qq_send_packet_logout(gc);
  }

  qq_sendqueue_free(qd);
	qq_info_query_free(qd);
	qq_add_buddy_request_free(qd);
	qq_updating_contact_info_free(qd);

  if (qd->fd >= 0) close(qd->fd);
  qd->fd = -1;

  // without it, segment fault after close 
  // because timeout callback still running
  if (qd->sendqueue_timeout) {
    g_source_remove(qd->sendqueue_timeout);
		qd->sendqueue_timeout = 0;
	}

  if (gc->inpa) { 
		gaim_input_remove(gc->inpa);
  	gc->inpa = 0;
	}
}

/*****************************************************************************/
// wrap login process for GAIM
static void 
qq_login(GaimAccount *account)
{
  GaimConnection *gc = gaim_account_get_connection(account);
	
  gaim_connection_update_progress
		(gc, _("Connecting"), 0, QQ_CONNECT_STEPS);                                                               
  gc->proto_data = g_new0(struct qq_data, 1);
	// use a modified gaim_proxy_connect to support UDP
  if (gaim_proxy_connect_full(account,
        gaim_account_get_string(account, "server", QQ_HOST),  
        gaim_account_get_int(account, "port", QQ_PORT),         
        qq_got_login, gc, 1) != 0) {                           
    gaim_connection_error(gc, _("Unable to connect."));          
  }
}

/*****************************************************************************/
static void qq_close(GaimConnection *gc) {
	struct qq_data *qd = gc->proto_data;
	qq_common_clean(gc);
	g_free(qd);
	gc->proto_data = NULL;
}

/*****************************************************************************/
// Generic GAIM protocol definition
static const char* qq_list_icon
(GaimAccount *a, GaimBuddy *b) {
	if ((! b) || (! b->proto_data))  return "qq";

	char *filename; 
	struct qq_buddy *fe = b->proto_data;
	filename = g_strdup_printf(_("qq_%d-%d"), fe->icon, fe->icon_suffix);
	return filename;
}

/*****************************************************************************/
static char *qq_status_text(GaimBuddy *b) {
	struct qq_buddy *q_bud = b->proto_data;
	if((!b) || (!q_bud)) return NULL;
	if (q_bud->status == QQ_BUDDY_ONLINE_AWAY)
		return g_strdup(_("Online (Away)"));
	else return NULL;
}

/*****************************************************************************/
static char *qq_tooltip_text(GaimBuddy *b) {
	struct qq_buddy *q_bud = b->proto_data;
	char gender_str[16];
	
	if ((!b) || (!q_bud)) return NULL;
	if (q_bud->gender == QQ_BUDDY_GENDER_GG) strcpy(gender_str, "GG");
	else if (q_bud->gender == QQ_BUDDY_GENDER_MM) strcpy(gender_str, "MM");
	else if (q_bud->gender == QQ_BUDDY_GENDER_UNKNOWN) 
		strcpy(gender_str, "*_*");
	else strcpy(gender_str, "^_^");
	
	if (strlen(q_bud->ip))
		return g_strdup_printf(
_("<small>Age: %d, Sex: %s\nIP: <span foreground=\"blue\">%s</span></small>"),
		q_bud->age, gender_str, q_bud->ip);
	else 
		return g_strdup_printf(
_("<small>Age: %d, Sex: %s</small>"), q_bud->age, gender_str);
}

/*****************************************************************************/
// this function add small icons above buddy icons to show its status
static void qq_list_emblems
(GaimBuddy *b, char **se, char **sw, char **nw, char **ne) {
	// each char ** are refering to filename in pixmaps/gaim/status/default/*png
	struct qq_buddy *q_bud = b->proto_data;
	if ((! b) || (!q_bud)) return;
	if (q_bud->flag & QQ_BUDDY_FLAG_QQ_MEMBER) {*sw = "activebuddy";}
	else if (q_bud->flag & QQ_BUDDY_FLAG_MOBILE) {*se = "wireless";}
	else if (q_bud->flag & QQ_BUDDY_FLAG_BIND_MOBILE) {*se = "wireless";}
}

/*****************************************************************************/
static GList *qq_away_states(GaimConnection *gc) {
	GList *m = NULL;

	m = g_list_append(m, _("QQ: Available"));
	m = g_list_append(m, _("QQ: Away"));
	m = g_list_append(m, _("QQ: Invisible"));
	m = g_list_append(m, GAIM_AWAY_CUSTOM);

	return m;
}

/*****************************************************************************/
static void qq_set_away
(GaimConnection *gc, const char *state, const char *msg) {
	struct qq_data *qd = (struct qq_data *)gc->proto_data;
	if (gc->away) {g_free(gc->away); gc->away = NULL;}
	
	if (msg) {
		qd->status = QQ_SELF_STATUS_CUSTOM;
		gc->away = g_strndup(msg, QQ_STATUS_MESSAGE_LENGTH_MAX);
	} else if (state) {
		gc->away = g_strdup("");
		if (!strcmp(state, _("QQ: Available")))
			qd->status = QQ_SELF_STATUS_AVAILABLE;
		else if (!strcmp(state, _("QQ: Away")))
			qd->status = QQ_SELF_STATUS_AWAY;
		else if (!strcmp(state, _("QQ: Invisible")))
			qd->status = QQ_SELF_STATUS_INVISIBLE;
		else if (!strcmp(state, GAIM_AWAY_CUSTOM)) {// should never here
			if (gc->is_idle) qd->status = QQ_SELF_STATUS_IDLE;
			else qd->status = QQ_SELF_STATUS_AVAILABLE;
		}
	} else if (gc->is_idle) qd->status = QQ_SELF_STATUS_IDLE;
		else qd->status = QQ_SELF_STATUS_AVAILABLE;

	qq_send_packet_change_status(gc);
}

/*****************************************************************************/
static int qq_send_im
(GaimConnection *gc, const char *who, 
 const char *message, GaimConvImFlags flags) {
	int type, to_uid;
	gchar *msg;
	
	struct qq_data *qd = gc->proto_data;
	
	if (strlen(message) > QQ_MSG_IM_MAX) return -E2BIG;
	type = (flags == GAIM_CONV_IM_AUTO_RESP ? QQ_IM_AUTO_REPLY : QQ_IM_TEXT);
	to_uid = atoi(who);
	
	if (to_uid != qd->uid) { // not  to myself
		// in fact if we send message directly to QQ2003, it also works
		msg = locale_to_qq(message, strlen(message), QQ_DEFAULT_LOCALE);
		qq_send_packet_im(gc, to_uid, msg, strlen(msg), type);
		g_free(msg);
	} else // it is too me
		serv_got_im(gc, who, message, flags, time(NULL));
		
	return 1;
}

/*****************************************************************************/
static void qq_get_info(GaimConnection *gc, const char *who) {
	struct qq_data *qd = gc->proto_data;
	struct _updating_contact_info *updateinfo;

	int uid = atoi(who);

	if(! uid) {
		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Not valid QQid: %s\n", who);
		return;
	}
	updateinfo = g_new0(struct _updating_contact_info, 1);
	updateinfo->uid = uid;
	qd->updateinfo = g_list_append(qd->updateinfo, updateinfo);

	qq_send_packet_get_info(gc, uid, TRUE); // need to show up info window
}

/*****************************************************************************/
void qq_get_my_info(GaimConnection *gc) {
	struct qq_data *qd = (struct qq_data *)gc->proto_data;
	gchar uid[16];
	
	if (!qd) return;
	
	sprintf(uid, "%d", qd->uid);
	qq_get_info(gc, uid);
}

/*****************************************************************************/
static GList *qq_actions(GaimConnection *gc) {
	GList *m = NULL;
	struct proto_actions_menu *pam;

	pam = g_new0(struct proto_actions_menu, 1);
	pam->label = utf8("修改个人资料");
	pam->callback = qq_get_my_info;
	pam->gc = gc;
	m = g_list_append(m, pam);

	return m;
}

/*****************************************************************************/
static void qq_add_buddy
(GaimConnection *gc, const char *name, GaimGroup *group) {
  struct qq_data *qd = gc->proto_data;
  if (! qd->logged_in) return;
  
  if (!atoi(name))
    gaim_notify_error(gc, NULL, 
    utf8("输入错误"), utf8("用户QQ号码无效，应该全为0-9数字")); 
  else 
    qq_send_packet_add_buddy(gc, name);
}

/*****************************************************************************/
static GaimPlugin *my_protocol = NULL;
static GaimPluginProtocolInfo prpl_info = {
	NULL,                                 /* 1 Protocol options */
	NULL,                                 /* 2 GList of GaimAccountUserSplit */
	NULL,                                 /* 3 GList of GaimAccountOption    */
	NULL,                                 /* 4 protocol specific options */
  qq_list_icon, // 5 This must be defined and return non-NULL string
	qq_list_emblems, // 6
	qq_status_text, // 7
	qq_tooltip_text, // 8
	qq_away_states, // 9
	qq_actions, // 10
	
	// 11. GList *(*buddy_menu)(GaimConnection *, const char *);
	NULL, // 11
	
	// 12. GList *(*chat_info)(GaimConnection *);
	NULL, // 12
	
	qq_login, // 13
	qq_close, // 14
	qq_send_im, // 15
  
  // 16. void (*set_info)(GaimConnection *, const char *info);	
	NULL, // 16
	
	// 17. int  (*send_typing)(GaimConnection *, const char *name, int typing);
	NULL, // 17
	
	qq_get_info,
	qq_set_away, // 19
	
	//20. void (*get_away)(GaimConnection *, const char *who);
	NULL, // 20
	
	/* 21. void (*set_dir)(GaimConnection *, const char *first,
   *                 const char *middle, const char *last,
   *                 const char *maiden, const char *city,
   *                 const char *state, const char *country, int web);
   */
	NULL, // 21
	
	// 22. void (*get_dir)(GaimConnection *, const char *who);
	NULL, // 22
  
  /* 23. void (*dir_search)(GaimConnection *, const char *first,
   *                    const char *middle, const char *last,
   *                    const char *maiden, const char *city,
   *                    const char *state, const char *country,
   *                    const char *email);
   */	
	NULL, // 23
	
	// 24. void (*set_idle)(GaimConnection *, int idletime);
	NULL, // 24
	
	/* 25. void (*change_passwd)(GaimConnection *, const char *old_pass,
   *                          const char *new_pass);
   */
	NULL, // 25
	
	// 26. void (*add_buddy)(GaimConnection *, const char *name, GaimGroup *group);
	qq_add_buddy, // 26
	
	// 27. void (*add_buddies)(GaimConnection *, GList *buddies);
	NULL, // 27
	
	/* 28. void (*remove_buddy)(GaimConnection *, const char *name,
   *                     const char *group);
   */
	NULL, // 28
	
	/* 29. void (*remove_buddies)(GaimConnection *, GList *buddies,
   *                        const char *group);
   */
	NULL, // 29
	
	// 30. void (*add_permit)(GaimConnection *, const char *name);
	NULL, // 30
	
	// 31. void (*add_deny)(GaimConnection *, const char *name);
	NULL, // 31
	
	// 32. void (*rem_permit)(GaimConnection *, const char *name);
	NULL, //32
	
	// 33. void (*rem_deny)(GaimConnection *, const char *name);
	NULL, // 33
	
	// 34. void (*set_permit_deny)(GaimConnection *);
	NULL, // 34
	
	// 35. void (*warn)(GaimConnection *, const char *who, int anonymous);
	NULL, // 35
	
	// 36. void (*join_chat)(GaimConnection *, GHashTable *components);
	NULL, // 36
	
	/* 37. void (*chat_invite)(GaimConnection *, int id,
   *                     const char *who, const char *message);
   */                     
	NULL, // 37
	
	// 38. void (*chat_leave)(GaimConnection *, int id);
	NULL, // 38
	
	/* 39. void (*chat_whisper)(GaimConnection *, int id,
   *                      const char *who, const char *message);
   */
	NULL, // 39
	
	// 40. int  (*chat_send)(GaimConnection *, int id, const char *message);
	NULL, // 40
	
	qq_send_packet_keep_alive, // 41
	
	// 42. void (*register_user)(GaimAccount *);
	NULL, // 42
	
	// 43. get chat buddy info and away message
	// void (*get_cb_info)(GaimConnection *, int, const char *who);
	NULL, // 43
	
	// 44. void (*get_cb_away)(GaimConnection *, int, const char *who);
	NULL, // 44
	
	/* 45 void (*alias_buddy)(GaimConnection *, const char *who,
   *                     const char *alias);
   */
	NULL, // 45
	
	/* 46. void (*group_buddy)(GaimConnection *, const char *who,    
   *                     const char *old_group, const char *new_group);  
   */
	NULL, // 46
	
	/* 47. void (*rename_group)(GaimConnection *, const char *old_group,
   *                      const char *new_group, GList *members);
   */
	NULL, // 47
	
	// 48. void (*buddy_free)(GaimBuddy *); 
	NULL, // 48
	
	// 49. void (*convo_closed)(GaimConnection *, const char *who);
	NULL, // 49
	
	// 50. const char *(*normalize)(const GaimAccount *, const char *);
	NULL, // 50
	
	// 51. void (*set_buddy_icon)(GaimConnection *, const char *filename);
	NULL, // 51
	
	// 52. void (*remove_group)(GaimConnection *gc, const char *group);
	NULL, // 52
	
	/* 53. char *(*get_cb_real_name)
	 * (GaimConnection *gc, int id, const char *who);
	 */
	NULL, // 53
	
	// 54. void (*set_chat_topic)(GaimConnection *gc, int id, const char *topic);
	NULL, // 54
	
	// 55. GaimChat *(*find_blist_chat)(GaimAccount *account, const char *name);
	NULL, // 55
	
	// 56. struct _GaimRoomlist *(*roomlist_get_list)(GaimConnection *gc);
	NULL, // 56
	
	// 57. void (*roomlist_cancel)(struct _GaimRoomlist *list); 
	NULL, // 57
	
	/* 58. void (*roomlist_expand_catagory)(struct _GaimRoomlist *list, 
	 *                                  struct _GaimRoomlistRoom *catagory);
	 */
	NULL // 58
};

/*****************************************************************************/
static GaimPluginInfo info = {
	2,                                                /**< api_version    */
	GAIM_PLUGIN_PROTOCOL,                             /**< type           */
	NULL,                                             /**< ui_requirement */
	0,                                                /**< flags          */
	NULL,                                             /**< dependencies   */
	GAIM_PRIORITY_DEFAULT,                            /**< priority       */
	
	"prpl-qq",                                        /**< id             */
	"QQ",                                             /**< name           */
	VERSION,                                          /**< version        */
	                                                  /**  summary        */
	N_("QQ Protocol Plugin"),
	                                                  /**  description    */
	N_("QQ Protocol Plugin"),
	NULL,                                             /**< author         */
	GAIM_WEBSITE,                                     /**< homepage       */

	NULL,                                             /**< load           */
	NULL,                                             /**< unload         */
	NULL,                                             /**< destroy        */

	NULL,                                             /**< ui_info        */
	&prpl_info                                        /**< extra_info     */
};

/*****************************************************************************/
static void
init_plugin(GaimPlugin *plugin) {
	GaimAccountOption *option;

	option = gaim_account_option_string_new(_("QQ host"), "server", QQ_HOST);
	prpl_info.protocol_options = 
    g_list_append(prpl_info.protocol_options, option);

	my_protocol = plugin;
}
GAIM_INIT_PLUGIN(qq, init_plugin, info);
